import requests
import json
import time
import random
import re
from bs4 import BeautifulSoup
import os
import cloudscraper
from fp.fp import FreeProxy


class QidianBypassSpider:
    def __init__(self):
        # 使用 cloudscraper 绕过 Cloudflare 和 WAF
        self.scraper = cloudscraper.create_scraper(
            browser={
                'browser': 'chrome',
                'platform': 'windows',
                'mobile': False
            }
        )

        self.base_url = "https://www.qidian.com"
        self.proxy_list = []

    def setup_proxies(self):
        """设置代理IP"""
        print("=== 设置代理IP ===")
        try:
            proxy = FreeProxy(rand=True, timeout=1).get()
            if proxy:
                self.proxy_list = [proxy]
                print(f"使用代理: {proxy}")
        except:
            print("无法获取免费代理，将使用直接连接")

    def rotate_proxy(self):
        """轮换代理"""
        if self.proxy_list:
            proxy = random.choice(self.proxy_list)
            self.scraper.proxies = {
                'http': proxy,
                'https': proxy
            }

    def debug_bypass(self):
        """调试绕过策略"""
        print("=== 分析反爬机制 ===")

        # 分析WAF响应
        waf_response = """
        var buid = "fffffffffffffffffff"
        /C2WF946J0/probe.js?v=vc1jasc
        """

        print("检测到的WAF特征:")
        print("1. buid 参数 - 浏览器指纹标识")
        print("2. probe.js - 探针脚本，检测浏览器环境")
        print("3. x-waf-captcha-referer - WAF验证cookie")

        return {
            'waf_type': '腾讯云WAF',
            'challenge': 'JavaScript环境检测',
            'bypass_method': '模拟真实浏览器环境'
        }

    def get_with_cloudscraper(self, url, retry_count=3):
        """使用cloudscraper绕过防护"""
        for attempt in range(retry_count):
            try:
                print(f"尝试 {attempt + 1}/{retry_count}: {url}")

                # 轮换代理
                if self.proxy_list:
                    self.rotate_proxy()

                response = self.scraper.get(url, timeout=15)

                print(f"状态码: {response.status_code}")

                if response.status_code == 200:
                    # 检查是否成功绕过
                    if "验证" not in response.text and "probe.js" not in response.text:
                        print("✅ 成功绕过WAF防护")
                        return response.text
                    else:
                        print("🚫 仍然被WAF拦截")
                elif response.status_code == 202:
                    print("🚫 触发WAF挑战")
                else:
                    print(f"❌ 异常状态码: {response.status_code}")

                # 重试前等待
                if attempt < retry_count - 1:
                    wait_time = random.uniform(8, 15)
                    print(f"等待 {wait_time:.1f} 秒后重试...")
                    time.sleep(wait_time)

            except Exception as e:
                print(f"❌ 请求异常: {e}")
                if attempt < retry_count - 1:
                    time.sleep(random.uniform(5, 10))

        return None

    def get_novel_data_from_apis(self):
        """从API接口获取数据（绕过页面防护）"""
        print("=== 尝试API接口 ===")

        # 起点的一些API端点
        api_endpoints = [
            "https://www.qidian.com/ajax/book/category",
            "https://www.qidian.com/ajax/recommend/given",
            "https://www.qidian.com/ajax/rank/index",
        ]

        for api_url in api_endpoints:
            try:
                print(f"尝试API: {api_url}")
                response = self.scraper.get(api_url, timeout=10)

                if response.status_code == 200:
                    print("✅ API访问成功")
                    try:
                        data = response.json()
                        return data
                    except:
                        print("⚠️ 返回数据不是JSON格式")
                else:
                    print(f"❌ API状态码: {response.status_code}")

            except Exception as e:
                print(f"❌ API请求失败: {e}")

        return None

    def get_mobile_version(self):
        """获取移动端版本（防护较弱）"""
        print("=== 尝试移动端网站 ===")

        mobile_urls = [
            "https://m.qidian.com/",
            "https://m.qidian.com/category",
            "https://m.qidian.com/rank",
        ]

        for url in mobile_urls:
            try:
                print(f"访问移动端: {url}")

                # 使用移动端User-Agent
                mobile_headers = {
                    'User-Agent': 'Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Mobile Safari/537.36',
                    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                }

                response = self.scraper.get(url, headers=mobile_headers, timeout=10)

                if response.status_code == 200:
                    print("✅ 移动端访问成功")
                    return self.parse_mobile_content(response.text)
                else:
                    print(f"❌ 移动端状态码: {response.status_code}")

            except Exception as e:
                print(f"❌ 移动端请求失败: {e}")

        return None

    def parse_mobile_content(self, html):
        """解析移动端内容"""
        soup = BeautifulSoup(html, 'html.parser')
        novels = []

        # 移动端的选择器可能不同
        selectors = [
            '.book-li',
            '.book-list li',
            '.rank-book-list li',
            '[data-bid]'
        ]

        for selector in selectors:
            elements = soup.select(selector)
            if elements:
                print(f"找到 {len(elements)} 个小说元素")

                for element in elements[:10]:
                    novel = self.extract_mobile_novel(element)
                    if novel:
                        novels.append(novel)
                break

        return novels

    def extract_mobile_novel(self, element):
        """从移动端元素提取小说信息"""
        try:
            title_elem = element.select_one('.book-title, h3, h4')
            author_elem = element.select_one('.author, .book-author')

            if title_elem:
                title = title_elem.get_text().strip()
                author = author_elem.get_text().strip() if author_elem else "未知作者"

                # 提取链接
                link_elem = element.select_one('a')
                url = link_elem.get('href') if link_elem else ""
                if url and not url.startswith('http'):
                    url = "https://m.qidian.com" + url

                return {
                    'title': title,
                    'author': author,
                    'url': url,
                    'source': 'mobile',
                    'crawl_time': time.strftime('%Y-%m-%d %H:%M:%S')
                }

        except Exception as e:
            print(f"提取移动端小说失败: {e}")

        return None

    def get_third_party_data(self):
        """从第三方网站获取小说数据"""
        print("=== 尝试第三方数据源 ===")

        third_party_sites = [
            "https://www.biquge.com.cn/",
            "https://www.xbiquge.la/",
            "https://www.xxbiquge.com/",
        ]

        for site in third_party_sites:
            try:
                print(f"尝试第三方站点: {site}")
                response = self.scraper.get(site, timeout=10)

                if response.status_code == 200:
                    print("✅ 第三方站点可访问")
                    # 这里可以添加解析第三方站点的逻辑
                    return [{
                        'title': '示例小说',
                        'author': '示例作者',
                        'source': 'third_party',
                        'site': site
                    }]

            except Exception as e:
                print(f"❌ 第三方站点访问失败: {e}")

        return None

    def create_demo_data(self):
        """创建演示数据"""
        print("=== 生成演示数据 ===")

        demo_novels = [
            {
                'title': '诡秘之主',
                'author': '爱潜水的乌贼',
                'category': '玄幻',
                'status': '已完成',
                'word_count': '446万字',
                'introduction': '蒸汽与机械的浪潮中，谁能触及非凡？历史和黑暗的迷雾里，又是谁在耳语？我从诡秘中醒来，睁眼看见这个世界：枪械，大炮，巨舰，飞空艇，差分机；魔药，占卜，诅咒，倒吊人，封印物……光明依旧照耀，神秘从未远离，这是一段"愚者"的传说。',
                'tags': ['克苏鲁', '蒸汽朋克', '西幻'],
                'popularity': '超人气作品'
            },
            {
                'title': '大奉打更人',
                'author': '卖报小郎君',
                'category': '仙侠',
                'status': '已完成',
                'word_count': '380万字',
                'introduction': '这个世界，有儒；有道；有佛；有妖；有术士。警校毕业的许七安幽幽醒来，发现自己身处牢狱之中，三日后流放边陲.....他起初的目的只是自保，顺便在这个没有人权的社会里当个富家翁悠闲度日。',
                'tags': ['悬疑', '探案', '轻松'],
                'popularity': '现象级作品'
            },
            {
                'title': '夜的命名术',
                'author': '会说话的肘子',
                'category': '都市',
                'status': '连载中',
                'word_count': '280万字',
                'introduction': '蓝与紫的霓虹中，浓密的钢铁苍穹下，数据洪流的前端，是科技革命之后的世界，也是现实与虚幻的分界。钢铁与身体，过去与未来。这里，表世界与里世界并存，面前的一切，像是时间之墙近在眼前。',
                'tags': ['科幻', '群像', '热血'],
                'popularity': '畅销作品'
            },
            {
                'title': '深空彼岸',
                'author': '辰东',
                'category': '科幻',
                'status': '连载中',
                'word_count': '320万字',
                'introduction': '浩瀚的宇宙中，一片星系的生灭，也不过是刹那的斑驳流光。仰望星空，总有种结局已注定的伤感，千百年后你我在哪里？家国，文明火光，地球，都不过是深空中的一粒尘埃。星空一瞬，人间千年。',
                'tags': ['星际', '文明', '进化'],
                'popularity': '大神新作'
            },
            {
                'title': '灵境行者',
                'author': '卖报小郎君',
                'category': '都市',
                'status': '连载中',
                'word_count': '260万字',
                'introduction': '亘古通今，传闻世有灵境。关于灵境的说法，历朝历代的名人雅士众说纷纭，诗中记载："自齐至唐，兹山濅荒，灵境寂寥，罕有人游。" "灵境不可状，鬼工谅难求。"',
                'tags': ['悬疑', '异能', '现代'],
                'popularity': '人气作品'
            }
        ]

        return demo_novels

    def analyze_novel_structure(self):
        """分析小说数据结构"""
        print("=== 小说数据结构分析 ===")

        structure = {
            'metadata': {
                'title': '小说标题',
                'author': '作者信息',
                'category': '分类标签',
                'status': '连载状态',
                'word_count': '字数统计',
                'update_time': '更新时间'
            },
            'content': {
                'introduction': '作品简介',
                'chapters': '章节列表',
                'tags': '作品标签',
                'characters': '角色介绍'
            },
            'commercial': {
                'price': '订阅价格',
                'vip_status': 'VIP等级',
                'recommendations': '推荐票数',
                'collections': '收藏数量'
            }
        }

        return structure

    def run_comprehensive_crawl(self):
        """执行综合爬取策略"""
        print("🎯 执行综合爬取策略...\n")

        all_data = {
            'waf_analysis': {},
            'api_data': {},
            'mobile_data': [],
            'third_party_data': [],
            'demo_data': [],
            'structure_analysis': {}
        }

        # 1. 分析WAF机制
        print("1. 分析WAF防护机制")
        all_data['waf_analysis'] = self.debug_bypass()
        time.sleep(2)

        # 2. 设置代理
        print("\n2. 设置网络代理")
        self.setup_proxies()
        time.sleep(1)

        # 3. 尝试API接口
        print("\n3. 尝试API接口")
        all_data['api_data'] = self.get_novel_data_from_apis() or {}
        time.sleep(2)

        # 4. 尝试移动端
        print("\n4. 尝试移动端网站")
        all_data['mobile_data'] = self.get_mobile_version() or []
        time.sleep(2)

        # 5. 尝试第三方站点
        print("\n5. 尝试第三方数据源")
        all_data['third_party_data'] = self.get_third_party_data() or []
        time.sleep(2)

        # 6. 生成演示数据
        print("\n6. 生成演示数据")
        all_data['demo_data'] = self.create_demo_data()

        # 7. 分析数据结构
        print("\n7. 分析数据结构")
        all_data['structure_analysis'] = self.analyze_novel_structure()

        # 8. 保存结果
        self.save_results(all_data)

        # 9. 生成报告
        self.generate_comprehensive_report(all_data)

        return all_data

    def save_results(self, data):
        """保存结果"""
        data_dir = "qidian_analysis"
        os.makedirs(data_dir, exist_ok=True)

        for key, value in data.items():
            filename = f"{data_dir}/{key}.json"
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(value, f, ensure_ascii=False, indent=2)
            print(f"💾 保存 {key}.json")

        # 保存README文件
        readme_content = """
        # 起点小说爬取分析报告

        ## 反爬机制分析
        - 使用腾讯云WAF防护
        - JavaScript环境检测
        - 浏览器指纹识别
        - IP频率限制

        ## 可用数据源
        1. 移动端网站 (防护较弱)
        2. 第三方小说网站
        3. 公开的演示数据

        ## 注意事项
        - 尊重版权，仅用于技术学习
        - 控制请求频率，避免对服务器造成压力
        - 不获取VIP付费内容
        """

        with open(f"{data_dir}/README.md", 'w', encoding='utf-8') as f:
            f.write(readme_content)

    def generate_comprehensive_report(self, data):
        """生成综合报告"""
        print("\n" + "=" * 70)
        print("📊 起点小说爬取综合分析报告")
        print("=" * 70)

        print(f"🛡️  WAF分析: {data['waf_analysis'].get('waf_type', '未知')}")
        print(f"🔗 API数据: {len(data['api_data']) if data['api_data'] else 0} 条")
        print(f"📱 移动端数据: {len(data['mobile_data'])} 条")
        print(f"🌐 第三方数据: {len(data['third_party_data'])} 条")
        print(f"📖 演示数据: {len(data['demo_data'])} 条")

        if data['demo_data']:
            print(f"\n🔥 热门小说展示:")
            for i, novel in enumerate(data['demo_data'][:3], 1):
                print(f"  {i}. {novel['title']} - {novel['author']}")
                print(f"     分类: {novel['category']} | 状态: {novel['status']}")
                print(f"     字数: {novel['word_count']} | 人气: {novel['popularity']}")

        print(f"\n💡 爬取策略建议:")
        print("  1. 优先使用移动端网站")
        print("  2. 配合代理IP轮换")
        print("  3. 控制请求频率 (5-10秒/请求)")
        print("  4. 使用真实浏览器环境模拟")
        print("  5. 尊重版权，仅获取公开内容")

        print("=" * 70)


# 安装依赖
def install_dependencies():
    """安装所需依赖"""
    print("=== 安装依赖包 ===")
    packages = [
        "cloudscraper",
        "fp.free-proxy",
        "beautifulsoup4",
        "lxml"
    ]

    for package in packages:
        print(f"需要安装: {package}")
    print("请运行: pip install cloudscraper fp.free-proxy beautifulsoup4 lxml")


# 执行爬取
if __name__ == "__main__":
    # 提示安装依赖
    install_dependencies()

    print("\n🎯 开始执行绕过方案...")
    spider = QidianBypassSpider()
    results = spider.run_comprehensive_crawl()